<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0, shrink-to-fit=no">
	<title>physics-world</title>
	<style>
		* {
			margin: 0;
			padding: 0;
		}
		html,body {
			height: 100%;
		}
		body {
			font-size: 14px;
			font-family: "Arial","Microsoft YaHei","黑体",sans-serif;
			overflow: hidden;
		}
		html,body,.main-page {
			position: relative;
			height: 100%;
			overflow: hidden;
		}
		.vr-btn {
			position: fixed;
			right: 18px;
			bottom: 18px;
			padding: 8px 12px;
			background-color: #00aadd;
			text-align: center;
			color: #fff;
			font-size: 14px;
			cursor: pointer;
			z-index: 100;
		}
	</style>
</head>
<body>
	<section class="main-page">
		<div class="vr-btn">进入VR</div>
	</section>
</body>
<script src="../vendor/webvr-polyfill.js"></script>
<script src="../vendor/three.min.js"></script>
<script src="../vendor/tween.min.js"></script>
<script src="../vendor/cannon.min.js"></script>
<script src="../common/vrbase.js"></script>
<script>
	/**
** author:YoneChen
** date:2017-08-18
**/
	const ASSETS = {
		PLANE_MAP: '../texture/road.jpg'
	};
	class WebVRApp extends VRBase {
		constructor() {
			super({
				renderElement: document.querySelector('.main-page'),
				buttonElement: document.querySelector('.vr-btn')
			});
		}
		initPhysicsWorld() {
			this.world = new CANNON.World();
			this.world.gravity.y = -10; // m/s²
			this.world.broadphase = new CANNON.NaiveBroadphase();
			this.physiclist = [];
		}
		start() {
			// 初始化物理引擎
			this.initPhysicsWorld();
			const { scene } = this;
			// 创建准心
			this.addCrosshair();
			// 创建光线
			scene.add(new THREE.AmbientLight(0xFFFFFF));
			this.addLight();
			// 创建地面
			this.addGround(1000, 1000);
			this.gazer = new Gazer();
			this.addBalls();
		}
		addCrosshair() {
			const { camera } = this;
			// 创建准心
			const geometry = new THREE.CircleBufferGeometry(0.002, 16);
			const material = new THREE.MeshBasicMaterial({
				color: 0xffffff,
				opacity: 0.5,
				transparent: true
			});
			const crosshair = new THREE.Mesh(geometry, material);
			crosshair.position.z = -0.5;
			camera.add(crosshair);
		}
		addBalls() {
			const { scene } = this;
			for (let i = 0; i < 60; i++) {
				// 创建立方体
				const material = new THREE.MeshLambertMaterial({
					color: 0xef6500,
					needsUpdate: true,
					opacity: 1,
					transparent: true
				});
				const geometry = new THREE.SphereGeometry(2, 12, 12);
				const ball = new THREE.Mesh(geometry, material);
				ball.castShadow = true;
				ball.receiveShadow = true;
				ball.position.set(30 * Math.random() - 15, 30 * Math.random() + 15, 30 * Math.random() - 15);
				scene.add(ball);
				this.addBody(ball, 1); // 质量为1
				this.gazer.on(ball, 'gazeEnter', () => {
					ball.material.opacity = 0.5;
				});
				this.gazer.on(ball, 'gazeLeave', () => {
					ball.material.opacity = 1;
				});
			}
		}
		addLight() {
			const { scene } = this;
			// 创建光线
			const light = new THREE.DirectionalLight(0xffffff, 1);
			light.position.set(30, 30, -30);
			light.castShadow = true;
			// light.shadow.camera.far = 5000;      // default
			light.shadow.mapSize.width = 4096;
			light.shadow.mapSize.height = 4096;
			light.shadow.camera.left = -350;
			light.shadow.camera.right = 350;
			light.shadow.camera.top = 350;
			light.shadow.camera.bottom = -350;
			scene.add(light);
		}
		addGround(width, height) {
			const { scene, world } = this;
			// 创建地平面
			const geometry = new THREE.PlaneBufferGeometry(width, height);
			const map = new THREE.TextureLoader().load(ASSETS.PLANE_MAP);
			const material = new THREE.MeshLambertMaterial({ map });
			material.map.repeat.set(10, 10);
			material.map.wrapS = THREE.RepeatWrapping;
			material.map.wrapT = THREE.RepeatWrapping;
			const ground = new THREE.Mesh(geometry, material);
			ground.rotation.x = - Math.PI / 2;
			ground.position.y = -10;
			ground.receiveShadow = true;
			scene.add(ground);
			this.addBody(ground, 0);
		}
		addBody(mesh, mass) {
			if (mesh.type != 'Mesh') return;
			const {
            geometry,
				position,
				quaternion
        } = mesh;
			let shape = null;
			const { parameters } = geometry;
			switch (geometry.type) {
				case 'BoxGeometry': shape = new CANNON.Box(new CANNON.Vec3(parameters.width, parameters.height, parameters.depth)); break;
				case 'SphereGeometry': shape = new CANNON.Sphere(parameters.radius); break;
				case 'PlaneBufferGeometry' || 'PlaneGeometry': shape = new CANNON.Plane(); break;
				default: return;
			}
			const body = new CANNON.Body({ mass });
			body.position.set(position.x, position.y, position.z);
			body.quaternion.set(quaternion.x, quaternion.y, quaternion.z, quaternion.w);
			body.addShape(shape);
			this.world.add(body);
			this.physiclist.push({
				id: mesh.id,
				mesh,
				body
			})
		}
		updatePhysics(dt) {
			const { world, physiclist } = this;
			this.world.step(1 / 60, dt);
			physiclist.map(({ mesh, body }) => {
				mesh.position.copy(body.position);
				mesh.quaternion.copy(body.quaternion);
			});
		}
		update() {
			const { scene, camera, renderer, clock } = this;
			const delta = clock.getDelta();
			this.updatePhysics(delta);
			// 启动渲染
			this.gazer.update(camera);
		}
	}
	// 凝视监听器
	class Gazer {
		constructor() {
			// 初始化射线发射源
			this.raycaster = new THREE.Raycaster();
			this._center = new THREE.Vector2();
			this.rayList = {}, this.targetList = [];
			this._lastTarget = null;
		}
		get target() {
			return this._lastTarget;
		}
		/** 
		 * @param {THREE.Mesh} target 监听的3d网格
		 * @param {String} eventType 事件类型 
		 *      'gazeEnter': '射线击中物体触发一次', 
		 *      'gazeTrigger': '射线击中物体触发', 
		 *      'gazeLeave': '射线离开物体触发'
		 * @param {Function} callback 事件回调
		 **/
		on(target, eventType, callback) {
			const noop = () => { };
			if (!this.rayList[target.id]) this.rayList[target.id] = {
				target,
				gazeEnter: noop,
				gazeTrigger: noop,
				gazeLeave: noop
			};
			this.rayList[target.id][eventType] = callback;
			this.targetList = Object.keys(this.rayList).map(key => this.rayList[key].target);
		}
		off(target, eventType) {
			if (!eventType) {
				delete this.rayList[target.id];
				this.targetList = Object.keys(this.rayList).map(key => this.rayList[key].target);
			} else {
				const noop = () => { };
				this.rayList[target.id][eventType] = noop;
			}
		}
		clear() {
			this.rayList = {}, this.targetList = [];
		}
		update(camera) {
			if (this.targetList.length <= 0) return;
			//更新射线位置
			this.raycaster.setFromCamera(this._center, camera);
			const intersects = this.raycaster.intersectObjects(this.targetList);

			if (intersects.length > 0) { // 当前帧射线击中物体
				const currentTarget = intersects[0].object;
				if (this._lastTarget) { // 上一帧射线击中物体
					if (this._lastTarget.id !== currentTarget.id) {
						// 上一帧射线击中物体与当前帧不同，触发上一帧物体的gazeLeave事件，触发当前帧物体的gazeEnter事件
						this.rayList[this._lastTarget.id].gazeLeave();
						this.rayList[currentTarget.id].gazeEnter();
					}
				} else { // 上一帧射线未击中物体
					this.rayList[currentTarget.id].gazeEnter(); // 上一帧射线没有击中物体，触发当前帧物体的gazeEnter事件
				}
				this.rayList[currentTarget.id].gazeTrigger(); // 当前帧射线击中物体，触发物体的gazeTrigger事件
				this._lastTarget = currentTarget;
			} else { // 当前帧我击中物体
				if (this._lastTarget) this.rayList[this._lastTarget.id].gazeLeave(); // 上一帧射线击中物体，触发上一帧物体gazeLeave
				this._lastTarget = null;
			}
		}
	}
	new WebVRApp();
</script>
</html>